﻿/*!
 * \file http_client.h
 * \date 2023/05/24 15:28
 *
 * \author Yunfeng.Bo
 * Contact: Yunfeng.Bo@qq.com
 *
 * \brief 基于 HTTP 的 http 和 websocket 客户端
 *
 * TODO
 *      1. 模拟同步方式调用的的 Web Socket RPC
 *      2. 基于回调的异步方式的 Web Socket RPC
 *      3. 根据路径，订阅消息
 *
 * \note
 */

#pragma once

// 使用 boost sinals2 库，发送消息
#include <boost/signals2/signal.hpp>
namespace sig2 = boost::signals2;

// 使用 libhv
#ifdef _MSC_VER
#    pragma warning(push)
#    pragma warning(disable : 4201)
#    pragma warning(disable : 4819)
#    pragma warning(disable : 4267)
#else
#    pragma GCC diagnostic push
// #    pragma GCC diagnostic ignored "-Wsign-compare"
#endif
#include "hv/WebSocketClient.h"
#include "hv/hlog.h"
#include "hv/requests.h"
#ifdef _MSC_VER
#    pragma warning(pop)
#else
#    pragma GCC diagnostic pop
#endif

#include "clock.h"
#include "http_rep.h"

// WebSocket 调用后，记录调用ID，并等待调用返回的数据
struct ws_call_req {
    uint32_t         call_id = 0;
    util::std_time_t call_time;
    util::std_time_t finish_time;
    bool             finished = false;
    std::string      data;
};

class ws_client : public hv::WebSocketClient {
public:
    ws_client(hv::EventLoopPtr loop = NULL)
        : hv::WebSocketClient(loop) {}
    ~ws_client() { disconnect(); }

    int connect(const char* url) {
        // set callbacks
        onopen = [this]() {
            const HttpResponsePtr& resp = getHttpResponse();

            // 发送消息
            on_connect(resp->body.c_str());
        };
        onmessage = [this](const std::string& msg) {
            // 发送消息
            on_message(opcode(), msg);

            if (WS_OPCODE_BINARY == opcode()) {
                // 所有 WebSocket 发来的二进制数据，一律自动视为远程调用的返回数据
                uint32_t call_id(0);
                memcpy(&call_id, msg.data(), sizeof(uint32_t));

                if (m_map_call.contains(call_id)) {
                    auto& c        = m_map_call[call_id];
                    c->finish_time = util::now();
                    c->data        = std::move(msg);
                    c->finished    = true;   // 最后设置调用结束标志
                }

                // LOG_TRACE << "response on call id: " << call_id;
            }
        };
        onclose = [this]() {
            // 发送消息 ??
            LOG_TRACE << "Websocket client closed!";
            // on_close();
        };

        // reconnect: 1,2,4,8,10,10,10...
        reconn_setting_t reconn;
        reconn_setting_init(&reconn);
        reconn.min_delay    = 1000;
        reconn.max_delay    = 10000;
        reconn.delay_policy = 2;
        setReconnect(&reconn);

        return open(url);
    };

    void disconnect() {
        // on_close.disconnect_all_slots();
        on_connect.disconnect_all_slots();
        on_message.disconnect_all_slots();

        if (!isConnected())
            return;

        close();

        // 这里需要等待关闭完成（关闭操作在另外的事件循环中，不等待会导致 on_close 事件发出失败）
        while (isConnected())
            std::this_thread::sleep_for(util::ms(10));
    }

    template<typename T, typename... Args>
    response_t<T> call(Args... args) {
        // 参数打包成 std::tuple
        using args_type = std::tuple<typename std::decay<Args>::type...>;
        args_type as    = std::make_tuple(args...);

        uint32_t call_id = m_id++;

        // 将 tuple 序列化成二进制数据
        msgpack::serializer sr;

        // 先放入ID
        sr << call_id;
        // 再放入所有的参数（func_name, ...）
        package_args(sr, as);

        // 发送消息
        int ret = send(sr.data(), (int)sr.size(), WS_OPCODE_BINARY);

        // 等待调用结果
        std::shared_ptr<ws_call_req> req(nullptr);
        auto                         n = util::now();
        if (ret)
            m_map_call[call_id] = req =
                std::make_shared<ws_call_req>(ws_call_req({call_id, n, n, false, ""}));


        if (!req)   // 返回通信错误
            return response_t<T>((int)rep_code::NET_FAILED);

        // 等待响应数据
        auto time_out = util::timeout(5);               // 超时时间：5秒
        while (!req->finished && util::time_diff(req->call_time, util::now()) < time_out)
            std::this_thread::sleep_for(util::ms(3));   // sleep 一下，再等等看

        if (!req->finished)                             // 返回超时
            return response_t<T>((int)rep_code::TIME_OUT);

        // 取调用结果
        msgpack::serializer sr_req(req->data.data() + sizeof(uint32_t),
                                   req->data.size() - sizeof(uint32_t));

        // 返回数据
        response_t<T> t;
        sr_req >> t;

        // 用后清除
        if (m_map_call.contains(call_id))
            m_map_call.erase(call_id);

        return t;
    }

public:
    // 信号：连接打开 resp->body.c_str()
    sig2::signal<void(const std::string&)> on_connect;
    // 信号：接收到服务端发来的消息
    sig2::signal<void(ws_opcode, const std::string&)> on_message;
    // 信号：连接关闭
    // sig2::signal<void()> on_close;

private:
    // 自增ID，用于标识不同的请求
    uint32_t m_id = 1;

    // 调用结果等待表，临时缓存调用请求返回的数据，完成后清除
    std::map<uint32_t, std::shared_ptr<ws_call_req>> m_map_call;
};

class http_client {
public:
    /*!
     * \brief 本接口特地用来调用返回值都是 JSON 类型的 HTTP 远程调用，且返回值的形式是：
     *        {
     *             "code": 200,
     *             "message": "success",
     *             "data": ...
     *        }
     *
     * \author Yunfeng.Bo
     * \date 2023/05/24 16:18
     */
    static json_rep json_get(const std::string& url, http_headers& headers) {
        headers["Content-Type"] = "application/json";

        auto r = requests::get(url.c_str(), headers);

        // 网络通信错误
        if (r == nullptr)
            return HTTP_UNKNOWN_ERR;

        if (HTTP_STATUS_OK != r->status_code)
            return HTTP_NET_ERR(r->status_code);

        json_rep ret;

        int          code(0);
        std::string  msg;
        json::value* data(nullptr);

        // 返回数据 -> JSON
        json::object result;
        try {
            str_to_json_object(r->Body(), result);

            // 解析返回数据
            get_int(result, "code", code);
            get_string(result, "message", msg);

            // 错误：服务器返回登录失败
            data = result.if_contains("data");
            if (!data)
                ret = HTTP_REMOTE_FAILED(code, msg, data);
        } catch (json_rep& x) {
            ret = x;                             // 返回 erc 对象给外部调用者
        } catch (boost::system::system_error& er) {
            ret = HTTP_FUNC_FAILED(er.what());   // boost system错误，通常是系统或功能调用失败
        } catch (const std::exception& e) {
            ret = HTTP_OTHER_EXP(e.what());   // 其它通用的异常
        } catch (...) {
            ret = HTTP_UNKNOWN_ERR;           // 未知异常
        }

        if (ret)
            return ret.reactivate();
        else
            return json_rep(code == 200 ? 0 : code, msg, data);
    }

    static json_rep json_get(const std::string& url) {
        http_headers headers;
        return json_get(url, headers);
    }

    /*!
     * \brief 本接口特地用来调用参数和返回值都是 JSON 类型的 HTTP 远程调用，且返回值的形式是：
     *        {
     *             "code": 200,
     *             "message": "success",
     *             "data": ...
     *        }
     *
     * \author Yunfeng.Bo
     * \date 2023/05/24 16:18
     */
    static json_rep json_post(const std::string& url, http_headers& headers, http_body& body) {
        headers["Content-Type"] = "application/json";

        auto r = requests::post(url.c_str(), body, headers);

        // 网络通信错误
        if (r == nullptr)
            return HTTP_UNKNOWN_ERR;

        if (HTTP_STATUS_OK != r->status_code)
            return HTTP_NET_ERR(r->status_code);

        json_rep ret;

        int          code(0);
        std::string  msg;
        json::value* data(nullptr);

        // 返回数据 -> JSON
        json::object result;
        try {
            str_to_json_object(r->Body(), result);

            // 解析返回数据
            get_int(result, "code", code);
            get_string(result, "message", msg);

            // 错误：服务器返回登录失败
            data = result.if_contains("data");
            if (data == nullptr)
                ret = HTTP_REMOTE_FAILED(code, msg, data);
        } catch (json_rep& x) {
            ret = x;                             // 返回 erc 对象给外部调用者
        } catch (boost::system::system_error& er) {
            ret = HTTP_FUNC_FAILED(er.what());   // boost system错误，通常是系统或功能调用失败
        } catch (const std::exception& e) {
            ret = HTTP_OTHER_EXP(e.what());   // 其它通用的异常
        } catch (...) {
            ret = HTTP_UNKNOWN_ERR;           // 未知异常
        }

        if (ret)
            return ret.reactivate();
        else
            return json_rep(code == 200 ? 0 : code, msg, data);
    }

    static json_rep json_post(const std::string& url, http_body& body) {
        http_headers headers;
        return json_post(url, headers, body);
    }

    static json_rep json_post(const std::string& url, http_headers& headers) {
        http_body body;
        return json_post(url, headers, body);
    }

    static json_rep json_post(const std::string& url) {
        http_headers headers;
        http_body    body;
        return json_post(url, headers, body);
    }

    /*!
     * \brief 本接口用来上传文件
     *
     * \author Yunfeng.Bo
     * \date 2023/06/01 14:18
     */
    static json_rep file_upload(const std::string& url, std::map<std::string, std::string>& params,
                                const std::string& filename) {
        auto r = requests::uploadFormFile(url.c_str(), "file", filename.c_str(), params);
        if (r == nullptr)
            return HTTP_UNKNOWN_ERR;

        if (HTTP_STATUS_OK != r->status_code)
            return HTTP_NET_ERR(r->status_code);

        json_rep ret;

        int          code(0);
        std::string  msg;
        json::value* data(nullptr);

        // 返回数据 -> JSON
        json::object result;
        try {
            str_to_json_object(r->Body(), result);

            // 解析返回数据
            get_int(result, "code", code);
            get_string(result, "message", msg);

            // 错误：服务器返回登录失败
            data = result.if_contains("data");
            if (data == nullptr)
                ret = HTTP_REMOTE_FAILED(code, msg, data);
        } catch (json_rep& x) {
            ret = x;                             // 返回 erc 对象给外部调用者
        } catch (boost::system::system_error& er) {
            ret = HTTP_FUNC_FAILED(er.what());   // boost system错误，通常是系统或功能调用失败
        } catch (const std::exception& e) {
            ret = HTTP_OTHER_EXP(e.what());   // 其它通用的异常
        } catch (...) {
            ret = HTTP_UNKNOWN_ERR;           // 未知异常
        }

        if (ret)
            return ret.reactivate();
        else
            return json_rep(code == 200 ? 0 : code, msg, data);
    }
};